package org.ws.httphelper.support.annotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ws.httphelper.HttpHelper;
import org.ws.httphelper.builder.RequestTemplateBuilder;
import org.ws.httphelper.builder.annotation.HttpClient;
import org.ws.httphelper.builder.annotation.HttpRequest;
import org.ws.httphelper.model.config.ClientConfig;
import org.ws.httphelper.model.config.RequestConfig;
import org.ws.httphelper.model.template.RequestTemplate;
import org.ws.httphelper.support.DefaultHttpHelper;
import org.ws.httphelper.template.DefaultClientConfig;
import org.ws.httphelper.template.DefaultRequestConfig;
import org.ws.httphelper.template.DefaultRequestTemplate;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

public class OperationProxy<T> implements InvocationHandler {

    private static Logger log = LoggerFactory.getLogger(OperationProxy.class.getName());

    private final Class<T> operationInterface;
    private final Map<Method,OperationMethod> operationMethodMap;
    private final HttpHelper parentHttpHelper;

    public OperationProxy(Class<T> operationInterface, Map<Method, OperationMethod> operationMethodMap) {
        this.operationInterface = operationInterface;
        this.operationMethodMap = operationMethodMap;
        RequestTemplate parentTemplate = buildParentTemplate();
        if(parentTemplate != null){
            this.parentHttpHelper = new DefaultHttpHelper(parentTemplate);
        }
        else {
            this.parentHttpHelper = null;
        }
    }

    private RequestTemplate buildParentTemplate(){
        HttpRequest httpRequestAnn = this.operationInterface.getAnnotation(HttpRequest.class);
        RequestConfig requestConfig = null;
        try {
            Map<String, Object> requestConfigMap = AnnotationUtils.getRequestConfigMap(httpRequestAnn);
            requestConfig = AnnotationUtils.makeRequestConfig(requestConfigMap);
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }

        HttpClient httpClientAnn = this.operationInterface.getAnnotation(HttpClient.class);
        ClientConfig clientConfig = AnnotationUtils.makeClientConfig(httpClientAnn);
        // 接口有两个注释,创建parent HttpHelper
        if(requestConfig != null && clientConfig != null){
            return RequestTemplateBuilder.builder()
                    .requestConfig(requestConfig)
                    .clientConfig(clientConfig)
                    .build();
        }
        else if(requestConfig != null){
            return RequestTemplateBuilder.builder()
                    .requestConfig(requestConfig)
                    .clientConfig(DefaultClientConfig.okHttpClientConfig(4,3_000))
                    .build();
        }
        else if(clientConfig != null){
            return RequestTemplateBuilder.builder()
                    .clientConfig(clientConfig)
                    .requestConfig(DefaultRequestConfig.getHtml())
                    .build();
        }
        // 无注解,继承接口
        else if(HttpHelper.class.equals(this.operationInterface)){
            return DefaultRequestTemplate.okHttpGetHtml();
        }
        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw t;
        }
        OperationMethod operationMethod = getMethodFromCache(method);
        return operationMethod.execute(args);
    }

    private OperationMethod getMethodFromCache(Method method){
        OperationMethod operationMethod = this.operationMethodMap.get(method);
        if(operationMethod == null){
            operationMethod = new OperationMethod(method,this.parentHttpHelper);
            this.operationMethodMap.put(method,operationMethod);
        }
        return operationMethod;
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
        throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
            .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
            .newInstance(declaringClass,
                MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                    | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
            .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }
}
